package com.example.demo.advice;

import com.alibaba.fastjson.JSON;
import com.example.demo.annotation.Encrypt;
import com.example.demo.config.SecretKeyConfig;
import com.example.demo.resp.RespMsg;
import com.example.demo.util.Base64Util;
import com.example.demo.util.JsonUtils;
import com.example.demo.util.RSAUtil;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * Author:Bobby
 * DateTime:2019/4/9
 **/
@Aspect
@Component
//@ConditionalOnClass(name = {"org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler"})
public class EncryptResponseBodyAdvice {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    private boolean encrypt;

    @Autowired
    private SecretKeyConfig secretKeyConfig;

    private static ThreadLocal<Boolean> encryptLocal = new ThreadLocal<>();

    private static MethodParameter param;

    @Autowired
    ServerCodecConfigurer serverCodecConfigurer;

    @Autowired
    RequestedContentTypeResolver requestedContentTypeResolver;


    @SneakyThrows
    //@Around(value = "execution(* org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler.handleResult(..)) && args(exchange, result)", argNames = "point,exchange,result")
    public Object handleResult(ProceedingJoinPoint point, ServerWebExchange exchange, HandlerResult result) {
//        Mono<RespMsg> body = ((Mono<RespMsg>) result.getReturnValue()).map(RespMsg::success)
        Mono<RespMsg> body = ((Mono<RespMsg>) result.getReturnValue()).map(msg -> {
            return RespMsg.success(msg.getData());
        })
                .defaultIfEmpty(RespMsg.success());
        Boolean status = encryptLocal.get();
        if (null != status && !status) {
            encryptLocal.remove();
            return point.proceed();
        }
        Mono<RespMsg> body2 = (Mono<RespMsg>) point.proceed();
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        encrypt = true;//method.isAnnotationPresent(Encrypt.class) && secretKeyConfig.isOpen();
        if (encrypt) {
            String publicKey = secretKeyConfig.getPublicKey();
//            body.map(content -> encry(publicKey, content));
//            body.subscribe();
            return point.proceed(Arrays.asList(exchange,
                    new HandlerResult(result.getHandler(), body.map(content -> encry(publicKey, content)), result.getReturnTypeSource())
            ).toArray());
        }
        return null;
    }

    private RespMsg encry(String publicKey, RespMsg content) {
        if (!StringUtils.hasText(publicKey)) {
            throw new NullPointerException("Please configure rsa.encrypt.privatekeyc parameter!");
        }
        String value = JSON.toJSONString(content.getData());
        log.info("before encry,value={} ", value);
        byte[] data = value.getBytes();
        String response = null;
        try {
            byte[] encodedData = RSAUtil.encrypt(data, publicKey);
            response = Base64Util.encode(encodedData);
        } catch (Exception e) {
            log.error("加密异常,e:{}", e);
        }
        if (secretKeyConfig.isShowLog()) {
            log.info("Pre-encrypted data：{}，After encryption：{}", content, response);
        }
        log.info("加密后,re:{}", response);
        content.setData(response);
        return content;
    }
}
